#include <cnet.h>
#include <cnetsupport.h>
#include <stdint.h>
#include <string.h>
#include <math.h>
#define PACKET_HEADERSIZE 6
#define ACK_HEADERSIZE 6
#define PINGPACKAGE_HEADERSIZE 6
#define ME (uint8_t) (nodeinfo.address)
#define TRUE ((bool)1)
#define FALSE ((bool)0)
#define min(x,y) ((x)<=(y)?(x):(y))
bool truth = TRUE;
bool lie = FALSE;

typedef struct PACKET {
	uint16_t crc16;
	uint8_t flags, src, dst, seq;
	char payload[10240]; /* 10240 bytes */
} PACKET; /* a data packet */

typedef struct ACK {
	uint16_t crc16;
	uint8_t flags, src, dst, seq;
} ACK; /* a multi purpose acknowledgement */

typedef struct PINGPACKAGE {
	uint16_t localcrc16;
	uint8_t flags;
	uint8_t src;
	uint16_t mtu;
} PINGPACKAGE;/* a package for routing purposes */

typedef struct PACKETINFO {
	size_t length;
	bool fromA;
	char *packet;
} PACKETINFO;

/* some forward declarations */
uint8_t getSrc(char* packet);
uint8_t getDst(char* packet);
void enableLinkTimer(int link);
bool put(int link, bool fromApp, char*item, size_t len);
void handlePACKET(int link, char*frame, size_t len);
void handleACK(int link, char*frame);
uint8_t getLink(int dst);
uint8_t mesnum(char*frame);
void acknowledge(int link,ACK*ack);
void enqueue(int dest, int mtu, char*msg, size_t len, int link);
void handleCON(char *msg, size_t len, uint8_t inc_Link);
PACKETINFO* giveme(int link);
PACKETINFO* givemevip(int link);
char *flagstostr(uint8_t flags);

bool ackable(char* frame) {
	switch ((uint8_t) frame[2]) {
	case 0:
	case 1:
	case 30:
	case 128:
	case 129:
	case 64:
	case 192: return 1;
	default: return 0;
	}
}

uint16_t crc(char*frame,size_t len){
	return CNET_crc16((unsigned char *) frame + sizeof(uint16_t),len - sizeof(uint16_t));
}

EVENT_HANDLER( physical_ready) {
	size_t len = MAX_MESSAGE_SIZE + PACKET_HEADERSIZE;
	char *frame = (char *) calloc(len, sizeof(char));
	int link;
	CHECK(CNET_read_physical(&link, frame, &len));
	uint8_t flags = ((uint8_t*) frame)[2];
	uint16_t localcrc16 = crc(frame,len);
	uint16_t crc16 = *(((uint16_t*)frame));
	if (localcrc16 != crc16) {
		free(frame);
		return;
	}
	switch (flags) {
	case 8:
	case 12:
	case 100:
	case 101:
	case 136:
	case 200:
	case 204:
		handleACK(link, frame);
		return;
	case 0:
	case 128:
	case 1:
	case 129:
		handlePACKET(link, frame, len);
		return;
	case 30:
		handleCON(frame, len, (uint8_t) link);
		return ;
	}
}

#define IP_RANGE 205
#define BUFFER_SIZE (size_t)(MAX_MESSAGE_SIZE)
#define WARNING_LEVEL (size_t)(BUFFER_SIZE/4)
#define WINDOWSIZE 100
#define VIP 10
#define defaulttimeout(i) ((CnetTime)(500000+4*linkinfo[i].propagationdelay))
#define defaulttimeoutstep(i) ((CnetTime)(2*linkinfo[i].propagationdelay))

void sent(PACKET *pkt);
uint8_t mesnum(char*frame);
bool isDone(PACKET *pkt);
HASHTABLE routingTable;
HASHTABLE minMTUtable;
static size_t freeSpace = BUFFER_SIZE;
static QUEUE *queues;
static QUEUE *prioqueues;

typedef struct SENDER {
	uint8_t index;
	CnetTimerID timer;
	CnetTime timeout;
	bool active;
	PACKETINFO **sendingWindow;
} SENDER;

SENDER **senderArray;

char *name(uint8_t ip){
	char *str = (char*)calloc(2,sizeof(char));
	str[0]=(char)ip;
	return str;
}

void* readHashtable(HASHTABLE ht,uint8_t ip){
	size_t len;
	char *str = name(ip);
	void *ptr;
	ptr = hashtable_find(ht,str,&len);
	free(str);
	return ptr;
}

void writeHashtable(HASHTABLE ht,uint8_t ip,void *item,size_t len){
	char *str = name(ip);
	hashtable_add(ht,str,item,len);
	free(str);
}

void enableLinkTimer(int link){
	if (!senderArray[link]->active){
		senderArray[link]->active = 1;
		senderArray[link]->timer = CNET_start_timer(EV_TIMER1, (CnetTime)1,(CnetData)(link));
	}
}

void sending(CnetEvent ev, CnetTimerID timer, CnetData data) {
	static bool workdone = FALSE;
	int index = senderArray[data]->index++;
	if (index>=WINDOWSIZE) { /* reached end of sending window */
		senderArray[data]->index = 0; /* reset index */
		if (workdone) CNET_start_timer(EV_TIMER1, (CnetTime)senderArray[data]->timeout,data); /* restart only if work done */
		else {
			senderArray[data]->active = 0; /* deactivate otherwise */
		}
		workdone = FALSE; /* reset workdone */
		return;
	}
	PACKETINFO *info = senderArray[data]->sendingWindow[index];
	CnetTime timeout = (CnetTime)0;
	if (info==NULL) { /* no work at this index */
		senderArray[data]->sendingWindow[index] = info = (index<(WINDOWSIZE-VIP))?giveme(data):givemevip(data); /* try to get a job */
		if (info != NULL) { /* got a job */
			size_t len = info->length;
			char *pkt = info->packet;
			CHECK(CNET_write_physical(data,pkt,&len));
			timeout = (CnetTime)(len*(CnetTime)8000000 / linkinfo[data].bandwidth);
			workdone = TRUE; /* definitely done something */
			if (!ackable(pkt)) { /* free memory if awaiting no ack  */
				free((void*)pkt);
				pkt=NULL;
				free((void*)info);
				senderArray[data]->sendingWindow[index] = info = NULL;
				senderArray[data]->index--;
			}
		}

		CNET_start_timer(EV_TIMER1,(CnetTime)timeout+1,data);
	}  else {/* if there already is work at this index */
		size_t len = info->length;
		char *pkt = info->packet;
		CHECK(CNET_write_physical(data,pkt,&len));
		CNET_start_timer(EV_TIMER1, (CnetTime)(1+len*(CnetTime)8000000 / linkinfo[data].bandwidth),data);
		workdone = TRUE; /* definitely done something */
		if (!ackable(pkt)) { /* free memory if awaiting no ack (should not happen in this case) */
			free((void*)pkt);
			pkt=NULL;
			free((void*)info);
			senderArray[data]->sendingWindow[index] = info = NULL;
			senderArray[data]->index--;
		}
	}

}

uint16_t getCRC(char* packet) {
	return *((uint16_t *)packet);
}

uint8_t getType(char* packet) {
	return ((uint8_t *)packet)[2];
}

uint8_t getSrc(char* packet) {
	return ((uint8_t *)packet)[3];
}

uint8_t getDst(char* packet) {
	return ((uint8_t *)packet)[4];
}

uint8_t getSeq(char* packet) {
	uint8_t type = getType(packet);
	switch (type) {
	case 30:
	case 64:
	case 192: return 0;
	default: return ((uint8_t *)packet)[5];
	}
}

uint8_t ackflags(char*frame){
	switch ((uint8_t)frame[2]) {
	case 30: return (uint8_t) (freeSpace <= WARNING_LEVEL)?204:200;
	case 64:
	case 192:return (uint8_t) (136);
	case 0:
	case 1:
		return (uint8_t) (freeSpace <= WARNING_LEVEL)?12:8;
	case 128:
	case 129:
		return (uint8_t) (freeSpace <= WARNING_LEVEL)?101:100;
	}
	return 0;
}

uint8_t decontaminate(uint8_t flags){
	switch (flags) {
	case 12: return 8;
	case 101: return 100;
	case 204: return 200;
	default: return flags;
	}
}

uint8_t acktype(char*frame){
	switch ((uint8_t)frame[2]) {
	case 30: return (uint8_t) 200;
	case 0:
	case 1:
		return (uint8_t) 8;
	case 128:
	case 129:
		return (uint8_t) 100;
	case 64:
	case 192: return (uint8_t) 136;
	default: return decontaminate((uint8_t)frame[2]);
	}
}

void acknowledge(int link,ACK *ack){
	for (int i = 0; i < WINDOWSIZE; i++) {
		PACKETINFO *info = senderArray[link]->sendingWindow[i];
		if (info == NULL) continue;
		char *pkt = info->packet;
		uint8_t packettype = acktype(pkt);
		if (acktype((char*)ack)==packettype &&
				ack->src==getSrc(pkt) &&
				ack->dst==getDst(pkt) &&
				ack->seq == getSeq(pkt)
		)
		{
			if (packettype==(uint8_t)8 || packettype==(uint8_t)100) {
				sent((PACKET*)pkt); // record in history that it has been sent successfully
				if (ME==getSrc(pkt) && isDone((PACKET*)pkt))
					CHECK(CNET_enable_application(getDst(pkt)));
			}
			free(pkt);
			pkt=NULL;
			free(info);
			senderArray[link]->sendingWindow[i] = info=NULL;
			return;
		}
	}
}

PACKETINFO* giveme(int link) {
	size_t len = sizeof(PACKETINFO);
	if (queue_nitems(prioqueues[link]) > 0) {
		return ((PACKETINFO *) queue_remove(prioqueues[link], &len));
	} else {
		if (queue_nitems(queues[link]) > 0) {
			PACKETINFO *pI = (PACKETINFO *) queue_remove(queues[link],&len);
			if (freeSpace < MAX_MESSAGE_SIZE && !(pI->fromA))
				freeSpace+=pI->length;
			return (pI);
		}
	}
	return NULL;
}

PACKETINFO* givemevip(int link){
	size_t len = sizeof(PACKETINFO);
	if (queue_nitems(prioqueues[link]) > 0) {
		return ((PACKETINFO *) queue_remove(prioqueues[link], &len));
	} else return NULL;
}

PACKETINFO *getPacketInfo(int link, char*item, size_t len, int fromApp) {
	PACKETINFO *packetInfo = (PACKETINFO*) calloc(1, sizeof(PACKETINFO));
	packetInfo->length = len;
	packetInfo->packet = item;
	packetInfo->fromA = fromApp;
	return packetInfo;
}

bool put(int link, bool fromApp, char*item, size_t len) {
	bool akbar = ackable(item);
	PACKETINFO *packetInfo = getPacketInfo(link, item, len, fromApp);
	if (fromApp || !akbar) {
		queue_add(akbar?queues[link]:prioqueues[link],(void*)packetInfo,sizeof(PACKETINFO));
		enableLinkTimer(link);
		free((void*) packetInfo);
		return TRUE;
	} else if (freeSpace - len >= 0) {
		freeSpace -= len;
		queue_add(queues[link],(void*)packetInfo,sizeof(PACKETINFO));
		enableLinkTimer(link);
		free((void*) packetInfo);
		return TRUE;
	}
	free((void*) item);
	free((void*) packetInfo);
	return FALSE;
}

ACK *generateACK(char*frame) {
	ACK *ack = (ACK*) calloc(1, sizeof(ACK));
	PACKET *pkt = (PACKET*) frame;
	ack->flags = ackflags(frame);
	ack->src = pkt->src;
	ack->dst = pkt->dst;
	ack->seq = (uint8_t) 0;
	switch ((uint8_t) frame[2]) {
	case 0:
	case 1:
	case 128:
	case 129: ack->seq = pkt->seq;
	}
	ack->crc16 =crc((char*)ack,sizeof(ACK));
	return ack;
}

void handleACK(int link, char*frame) {
	ACK *ack = (ACK*) frame;
	switch(ack->flags) {
	case 12:
	case 204:
		senderArray[link]->timeout += defaulttimeoutstep(link);
		break;
	default:
		senderArray[link]->timeout = defaulttimeout(link);
		break;
	}
	acknowledge(link,ack);
	free(ack);
}

uint16_t getMmtu(int dst) {
	uint16_t *ptr = (uint16_t*)readHashtable(minMTUtable,dst);
	return ptr?*ptr:0;
}


uint8_t getLink(int dst) {
	uint8_t *ptr = (uint8_t*)readHashtable(routingTable,dst);
	return ptr?*ptr:0;
}

void informOthers(){
	for (int i = 1; i <= nodeinfo.nlinks; i++) {
		PINGPACKAGE *msg = (PINGPACKAGE*) calloc(1,PINGPACKAGE_HEADERSIZE);
		msg->flags = (uint8_t) 30;
		msg->src = ME;
		msg->mtu = linkinfo[i].mtu;
		msg->localcrc16 = crc((char *) msg,PINGPACKAGE_HEADERSIZE);
		put(i, 1, (char*) msg, PINGPACKAGE_HEADERSIZE);
	}
}

void inform(CnetEvent ev, CnetTimerID timer, CnetData data) {
	informOthers();
}

void handleCON(char *msg, size_t len, uint8_t inc_Link){
	PINGPACKAGE *pingPackage = (PINGPACKAGE*) msg;
	uint8_t source = (uint8_t) pingPackage->src;
	uint16_t mtu = (uint16_t) pingPackage->mtu;
	put(inc_Link,1,(char*)generateACK(msg),sizeof(ACK));
	free(msg);
	msg=NULL;
	if (nodeinfo.address == source || getLink(source)!=0){
		return;
	}

	writeHashtable(routingTable,source,&inc_Link,sizeof(uint8_t));
	writeHashtable(minMTUtable,source,&mtu,sizeof(uint16_t));
	static bool already_greeted = FALSE;
	if (!already_greeted) {
		already_greeted = TRUE;
		CNET_start_timer(EV_TIMER2,13370,0);
	}


	CHECK(CNET_enable_application(source));
	for (int i = 1; i<=nodeinfo.nlinks; i++ ){
		if (i==inc_Link) continue;
		PINGPACKAGE *newP = (PINGPACKAGE*) calloc(1,PINGPACKAGE_HEADERSIZE);
		newP->flags = (uint8_t) 30;
		newP->src = source;
		newP->mtu = min(mtu,linkinfo[i].mtu);
		newP->localcrc16 = crc((char *) newP,PINGPACKAGE_HEADERSIZE);
		put(i, 1, (char*) newP, PINGPACKAGE_HEADERSIZE);
	}
}

typedef struct ENTRY{
	uint8_t seq[114];
	uint8_t mesnum;
	uint8_t got;
	uint8_t sent;
	uint8_t exp;
} ENTRY;

static int cur_mesnum[IP_RANGE]; /* indicates current message number for given destination */
static size_t rcv_len[IP_RANGE]; /* cumulative received message length for given source */
static char* constructionsite[IP_RANGE]; /* buffer for incoming messages (without last packets) for given source */
static char* lastpieces[IP_RANGE]; /* buffer for last packet of incoming messages for given source */
static size_t last_len[IP_RANGE]; /* length of last packet of incoming message for given source */
static HASHTABLE history;
int inHistory(PACKET*pkt);

/* splits a message into packets and puts them into corresponding link-queue */
void enqueue(int dest, int mtu, char*msg, size_t len, int link) {
	int maxpayload = mtu - PACKET_HEADERSIZE; /* calculate maximum payload length */
	int num = ceil(len / (float) (maxpayload)); /* compute the number of parts */
	int rem = len % maxpayload; /* compute the size of the last part if it's not zero */
	int last = rem == 0 ? maxpayload : rem; /* compute the real size of the last part */
	for (int i = 0; i < num; i++) {
		PACKET *pkt = (PACKET*) calloc(1, sizeof(PACKET));
		pkt->dst = (uint8_t) dest;
		pkt->flags = (uint8_t)(i==num-1)?cur_mesnum[dest]==0?1:129:cur_mesnum[dest]==0?0:128;
		pkt->seq = i;
		pkt->src = ME;
		size_t payloadlength = (i==num-1)?last:maxpayload; /* payloadlength of current piece */
		memmove((void*) (pkt->payload), (void*) (msg + (i*maxpayload)), payloadlength);
		pkt->crc16 = crc((char *) pkt,PACKET_HEADERSIZE + payloadlength);
		inHistory(pkt);
		put(link, 1, (char*) pkt, PACKET_HEADERSIZE + payloadlength); /* put it into correct link-queue */
	}
	cur_mesnum[dest]^=1; /* flip message number */
	free(msg);
}

/* returns message number of given frame (zero or one) */
uint8_t mesnum(char*frame) {
	switch ((uint8_t) frame[2]) {
	case 128:
	case 129:
	case 192: return 1;
	default: return 0;
	}
}

/* returns whether given packet is a last one */
bool isLast(PACKET*pkt) {
	switch (pkt->flags) {
	case 1:
	case 129: return 1;
	default: return 0;
	}
}

/* returns whether this node has a complete message from given source in its input buffer */
bool isComplete(int src) {
	void *src_h,*dst;
	if ((src_h = readHashtable(history,src)))
		if ((dst = readHashtable(*((HASHTABLE*)src_h),ME))) {
			ENTRY *e = (ENTRY*)dst;
			if (e->exp && e->exp==e->got){
				e->sent=e->got=e->exp;
				return TRUE;
			}
		}
	return FALSE;
}

bool isDone(PACKET *pkt) {
	void *src,*dst;
	if ((src = readHashtable(history,pkt->src)))
		if ((dst = readHashtable(*((HASHTABLE*)src),pkt->dst))) {
			ENTRY *e = (ENTRY*)dst;
			if (e->exp) return (e->exp==e->sent);
		}
	return FALSE;
}

void sent(PACKET*pkt){
	void *src,*dst;
	if ((src = readHashtable(history,pkt->src)))
		if ((dst = readHashtable((*(HASHTABLE*)src),pkt->dst))) {
			ENTRY *e = (ENTRY*)dst;
			e->sent++;
		}
}

ENTRY *createEntry(PACKET*pkt){
	ENTRY *e = calloc(1,sizeof(ENTRY));
	e->mesnum=mesnum((char*)pkt);
	e->got=1;
	e->sent=0;
	e->exp=isLast(pkt)?pkt->seq+1:0;
	for (int i = 0; i < 114; ++i) {
		e->seq[i] = 0;
	}
	e->seq[pkt->seq] = 1;
	return e;
}

int inHistory(PACKET*pkt) {
	HASHTABLE src = NULL,dst = NULL;
	if ((src = readHashtable(history,pkt->src))){
		if ((dst = readHashtable(*((HASHTABLE*)src),pkt->dst))) {
			ENTRY *e = (ENTRY*)dst;
			if (e->mesnum==mesnum((char*)pkt)) {
				if (e->seq[pkt->seq]) {
					return 1;
				} else {
					e->seq[pkt->seq] = 1;
					e->got++;
					if (isLast(pkt)) e->exp = pkt->seq+1;
					return 0;
				}
			} else if (e->exp && e->exp==e->sent) {
				e->mesnum=mesnum((char*)pkt);
				e->got=1;
				e->sent=0;
				e->exp=isLast(pkt)?pkt->seq+1:0;
				for (int i = 0; i < 114; ++i) {
					e->seq[i] = 0;
				}
				e->seq[pkt->seq] = 1;
				return 0;
			} else {
				return 2;
			}
		} else {
			ENTRY *e = createEntry(pkt);
			char *str = name(pkt->dst);
			hashtable_add(*((HASHTABLE*)src),str,e,sizeof(ENTRY));
			free(str);
			free(e);
			return 0;
		}
	} else {
		src = hashtable_new(43);
		char *str = name(pkt->src);
		hashtable_add(history,str,&src,sizeof(HASHTABLE));
		free(str);
		ENTRY *e = createEntry(pkt);
		str = name(pkt->dst);
		hashtable_add(src,str,e,sizeof(ENTRY));
		free(str);
		free(e);
		return 0;
	}
}

/* handles the case that the packet is destined for this node */
void receivePacket(int link, PACKET*pkt, size_t len) {
	int src = (int) (pkt->src);
	int seq = (int) (pkt->seq);
	size_t payloadlen = len - PACKET_HEADERSIZE; /* calculate payload length */
	if (isLast(pkt)) { /* if it's the last one */
		memmove(lastpieces[src],pkt->payload, payloadlen); /* store the last packet extra */
		last_len[src] = payloadlen; /* store the length of the last piece */
	} else {
		memmove(constructionsite[src]+seq*payloadlen,pkt->payload,payloadlen); /* write packet into correct place */
		rcv_len[src]+=payloadlen; /* update received payload length */
	}
	if (isComplete(src)) { /* if the message is now complete */
		memmove(constructionsite[src] + rcv_len[src],lastpieces[src],last_len[src]); /* copy last piece */
		rcv_len[src]+=last_len[src]; /* complete received payload length */
		CHECK(CNET_write_application(constructionsite[src],&(rcv_len[src]))); /* forward data to application */
		rcv_len[src]=0; /* reset received length */
		last_len[src]=0; /* reset length of the last piece */
	}
	free(pkt); /* free packet memory */
}

/* handles the arrival of a data packet */
void handlePACKET(int link, char*frame, size_t len) {
	PACKET *pkt = (PACKET*) frame;
	switch (inHistory(pkt)) {
	case 1:
		put(link, 1, (char*) generateACK(frame), sizeof(ACK));
	case 2:
		free(frame);
		return;
	}
	if (pkt->dst != ME) { /* if it's NOT for this node */
		if (put(getLink(pkt->dst), 0, frame, len)){ /* try to forward it */
			put(link, 1, (char*) generateACK(frame), sizeof(ACK)); /* acknowledge on success */
			return;
		} else {
			free(frame); /* otherwise drop it */
			return;
		}
	}
	/* if it is for me */
	put(link, 1, (char*) generateACK(frame), sizeof(ACK)); /* acknowledge arrival */
	receivePacket(link, pkt, len); /* store packet appropriately */
}

EVENT_HANDLER(application_ready) {
	CnetAddr dest;
	size_t len = MAX_MESSAGE_SIZE;
	char *msg = (char*) calloc(MAX_MESSAGE_SIZE, sizeof(char));
	CHECK(CNET_read_application(&dest, msg, &len)); /* read in message */
	CHECK(CNET_disable_application(dest)); /* prevent app from sending more message to same desination */
	enqueue(dest, getMmtu(dest), msg, len, getLink(dest)); /* if link known - enqueue */
}

EVENT_HANDLER( reboot_node) {
	senderArray  = (SENDER**)calloc(nodeinfo.nlinks+1, (sizeof(SENDER*)));
	history = hashtable_new(10);
	routingTable = hashtable_new(10);
	minMTUtable = hashtable_new(10);

	queues = (QUEUE *) calloc(nodeinfo.nlinks + 1, sizeof(QUEUE));
	prioqueues = (QUEUE *) calloc(nodeinfo.nlinks + 1, sizeof(QUEUE));

	for (int i = 0; i <= nodeinfo.nlinks; i++) {
		queues[i] = queue_new();
		prioqueues[i] = queue_new();
	}
	for (int i = 1; i <= nodeinfo.nlinks; i++) {
		SENDER *sender = (SENDER*) calloc(1,sizeof(SENDER));
		sender->index = 0;
		sender->timer = NULLTIMER;
		sender->timeout = defaulttimeout(i);
		sender->active = 0;
		sender->sendingWindow = (PACKETINFO**)calloc(WINDOWSIZE,sizeof(PACKETINFO*));
		senderArray[i]=sender;
	}
	for (int i = 0; i < IP_RANGE; i++) {
		cur_mesnum[i] = 0;
		rcv_len[i] = 0;
		constructionsite[i] = (char*) calloc(MAX_MESSAGE_SIZE, sizeof(char));
		lastpieces[i] = (char*) calloc(MAX_MESSAGE_SIZE, sizeof(char));
		last_len[i] = 0;
	}
	CHECK(CNET_set_handler(EV_APPLICATIONREADY, application_ready, 0));
	CHECK(CNET_set_handler(EV_PHYSICALREADY, physical_ready, 0));
	CHECK(CNET_set_handler(EV_TIMER1, sending, 0));
	CHECK(CNET_set_handler(EV_TIMER2, inform, 0));
	if (nodeinfo.nodenumber==0){
		informOthers();
	}
}
